﻿/*
 * zfilesys.cxx
 *
 *  Created on: 2014年10月3日
 *      Author: Fifi Lyu
 */

#include "zpsoe/zfilesys.h"
#include "zpsoe/zcmd.h"
#include "zpsoe/zlog.h"
#include "zpsoe/zerrno.h"
#include "zpsoe/zexception.h"
#include "zpsoe/zalgorithm/zstring.h"
#include <iostream>

using namespace std;

#ifdef PLATFORM_WIN32
    #include <direct.h>

    #ifndef INCLUDE_WINDOWS_H_FILE
        #define INCLUDE_WINDOWS_H_FILE
        #include <Windows.h>
    #endif
#else
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <dirent.h>
    #include <errno.h>
#endif

#include <sys/stat.h>
#include <cstring>
#include <fstream>

#ifdef PLATFORM_WIN32
    #define S_ISDIR(st_mode) (((st_mode) & S_IFMT) == S_IFDIR)
    #define S_ISLNK(st_mode) 0
    #define S_ISREG(st_mode) (((st_mode) & S_IFMT) == S_IFREG)
#endif

using zpsoe::zerrno::error_to_str;
using zpsoe::zalgorithm::zstring::to_lower;
using std::ifstream;
using std::ofstream;

namespace zpsoe {

namespace zfilesys {

ZPSOE_SHARED_LIB_API bool is_file(const string &path) {
    struct stat file_info_;

    if ((stat(path.c_str(), &file_info_) == 0))
        //TODO windows 测试链接
        return (S_ISREG(file_info_.st_mode) || S_ISLNK(file_info_.st_mode));

    ThrowZpsoeException(error_to_str());
}

ZPSOE_SHARED_LIB_API bool is_exist(const string &path) {
    struct stat stat_;
    return stat(path.c_str(), &stat_) == 0;
}

ZPSOE_SHARED_LIB_API bool is_dir(const string &path) {
    struct stat stat_;
    if ((stat(path.c_str(), &stat_) != 0))
        return false;
    return S_ISDIR(stat_.st_mode);
}

ZPSOE_SHARED_LIB_API bool _mk_dir(const string &path) {
    if (is_exist(path)) return true;

#ifdef PLATFORM_WIN32
    return (_mkdir(path.c_str()) == 0);
#else
    return (mkdir(path.c_str(), 0755) == 0);
#endif
}

ZPSOE_SHARED_LIB_API bool _rm_dir(const string &path) {
    if (!is_exist(path)) return true;

#ifdef PLATFORM_WIN32
    return (_rmdir(path.c_str()) == 0);
#else
    return (rmdir(path.c_str()) == 0);
#endif
}

//FIXME bool or exception? 设置last error，保存错误

ZPSOE_SHARED_LIB_API bool rm_dir(const string &path, const bool recursive) {
    string name_;
    string path_;
    struct stat stat_;
    bool ret_ = true;

    if (path.empty()) return false;

    if (!recursive) return _rm_dir(path);

#ifdef PLATFORM_WIN32
    WIN32_FIND_DATA wfd_;
    HANDLE h_find_ = FindFirstFile(string(path + "\\*.*").c_str(), &wfd_);

    if (h_find_ == INVALID_HANDLE_VALUE) 
        return false;

    do {
        name_ = wfd_.cFileName;
#else
    DIR *dir_;
    struct dirent *d_;

    dir_ = opendir(path.c_str());

    if (dir_ == NULL)
        ThrowZpsoeException(zerrno::error_to_str());

    // 遍历目录，找出目录和文件
    while ((d_ = readdir(dir_))) {
        name_ = d_->d_name;
#endif

        // 跳过 . 和 ..
        if (name_ == "." || name_ == "..")
            continue;

        path_ = path + OS_SEPARATOR +  name_;

        // 检测能否获取文件信息
        if (stat(path_.c_str(), &stat_) != 0) {
            ret_ = false;
            break;
        }

        if (S_ISDIR(stat_.st_mode)) {
            // 先删除空目录，失败则以迭代方式删除
            if(!_rm_dir(path_) && !rm_dir(path_, true)) {
                ret_ = false;
                break;
            }
        } else if (S_ISREG(stat_.st_mode) || S_ISLNK(stat_.st_mode)) {
            if (!delete_file(path_)) {
                ret_ = false;
                break;
            }
        } else { // neither a directory nor file
            ret_ = false;
            break;
        }
#ifdef PLATFORM_WIN32
    } while (FindNextFile(h_find_, &wfd_));

    FindClose(h_find_);
#else
    }

    closedir(dir_);
#endif

    return ret_;
}

ZPSOE_SHARED_LIB_API bool create_file(const string &path) {
    if (is_exist(path)) return true;

#ifdef PLATFORM_WIN32
    HANDLE h_ = CreateFile(
            path.c_str(),         // file name
            GENERIC_WRITE,        // open for write
            0,                    // do not share
            NULL,                 // default security
            CREATE_ALWAYS,        // overwrite existing
            FILE_ATTRIBUTE_NORMAL,// normal file
            NULL);                // no template

    if (h_) {
        CloseHandle(h_);
        return true;
    }
#else
    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    int fd_ = creat(path.c_str(), mode);

    if (fd_) {
        close(fd_);
        return true;
    }
#endif

    print_log(LOG_ERROR, "%s", error_to_str().c_str());
    return false;
}

ZPSOE_SHARED_LIB_API bool delete_file(const string &path) {
    if (!is_exist(path)) return true;

#ifdef PLATFORM_WIN32
    bool ret = DeleteFile(path.c_str());
    DWORD f_ = GetLastError();
    return ret;
#else
    return (unlink(path.c_str()) == 0);
#endif
}

ZPSOE_SHARED_LIB_API byte_t get_size(const string &file_name) {
    struct stat buf_;

    if (stat(file_name.c_str(), &buf_) == 0)
        return buf_.st_size;

    ThrowZpsoeException(error_to_str());
}

ZPSOE_SHARED_LIB_API string read_file(const string &file_name) {
    string tmp;
    string file_content;
    ifstream infile;

    infile.open(file_name.c_str(), ifstream::binary);

    if (!infile.is_open())
        ThrowZpsoeException(error_to_str());

    // 以 '\n' 作为分行标识
    while (getline(infile, tmp)) {
        file_content += tmp;

        if (!infile.eof())
            file_content += OS_LINE_FEED;
    }

    infile.close();

    return file_content;
}

ZPSOE_SHARED_LIB_API void read_file(
        const string &file_name, vector<string> &lines) {
    lines.clear();
    string tmp;
    string file_content;
    ifstream infile;

    infile.open(file_name.c_str(), ifstream::binary);

    if (!infile.is_open())
        ThrowZpsoeException(error_to_str());

    // 以 '\n' 作为分行标识
    while (getline(infile, tmp)) {
        const size_t tmp_size_(tmp.size());

        if (tmp_size_ && tmp[tmp_size_ - 1] == '\r')
            tmp.erase(tmp_size_ - 1);

        lines.push_back(tmp);
        tmp.clear();
    }

    infile.close();
}

ZPSOE_SHARED_LIB_API void write_file(
        const string &file_name, const vector<string> &lines, const bool &append) {
    string content_("");

    for (vector<string>::const_iterator it_ = lines.begin(); it_ != lines.end(); ++it_)
        content_ += (*it_) + OS_LINE_FEED;

    write_file(file_name, content_, append);
}

ZPSOE_SHARED_LIB_API void write_file(
    const string &file_name, const string &file_content, const bool &append) {
    ofstream outfile;

    if (append)
        outfile.open(file_name.c_str(), ofstream::binary | ofstream::ate | ofstream::app);
    else
        outfile.open(file_name.c_str(), ofstream::binary);

    if (outfile)
        outfile << file_content;
    else
        ThrowZpsoeException(error_to_str());
}

ZPSOE_SHARED_LIB_API bool mk_dir(
        const string &path, const bool make_parents) {
    char tmp[256];
    char *p = NULL;
    size_t len;

    // 不创建父目录
    if (!make_parents)
        return _mk_dir(path);

    // 创建父目录

#ifdef PLATFORM_WIN32
    _snprintf(tmp, sizeof(tmp), "%s", path.c_str());
#else
    snprintf(tmp, sizeof(tmp), "%s", path.c_str());
#endif
    len = strlen(tmp);

    // 去掉最后一个路径分隔符
    if (tmp[len - 1] == OS_SEPARATOR_CHAR)
        tmp[len - 1] = 0;

    // tmp + 1 表示向前跳过第一个分隔符
    for (p = tmp + 1; *p; p++) {
        if (*p == OS_SEPARATOR_CHAR) {
            // 如果找到分隔符，在分隔符位置设置字符串结束符，截断字符串
            *p = 0;

            if (_mk_dir(string(tmp))) {
                // 恢复被截断的字符串
                *p = OS_SEPARATOR_CHAR;
                continue;
            }

            return false;
        }
    }

    // 创建最后一个文件夹
    return _mk_dir(string(tmp));
}

ZPSOE_SHARED_LIB_API void get_files_in_dir(
       const string &path, const file_type_t t,
       vector<file_stat_t> &v, const uint32_t max_num) {
    if (path.empty())
        ThrowZpsoeException("Invalid directory.");

    v.clear();

    uint32_t num_ = 0;
    file_type_t type_;
    string ext_;
    string path_;
    string name_;
    size_t sep_pos_;
    file_stat_t file_stat_;
    struct stat stat_;
#ifdef PLATFORM_WIN32
    WIN32_FIND_DATA wfd_;
    HANDLE h_find_ = FindFirstFile(string(path + "\\*.*").c_str(), &wfd_);

    if (h_find_ == INVALID_HANDLE_VALUE) 
        return;

    do {
        name_ = wfd_.cFileName;
#else
    DIR *dir_;
    struct dirent *d_;

    dir_ = opendir(path.c_str());

    if (dir_ == NULL)
        ThrowZpsoeException(zerrno::error_to_str());

    // 遍历目录，找出目录和文件
    while ((d_ = readdir(dir_))) {
        name_ = d_->d_name;
#endif
        // 跳过 . 和 ..
        if (name_ == "." || name_ == "..")
            continue;

        path_ = path + OS_SEPARATOR +  name_;

        // 检测能否获取文件信息
        if (stat(path_.c_str(), &stat_) != 0)
            ThrowZpsoeException(zerrno::error_to_str());

        if (S_ISDIR(stat_.st_mode))
            type_ = FT_DIR;
        else if (S_ISREG(stat_.st_mode) || S_ISLNK(stat_.st_mode)) {
            type_ = FT_FILE;
            // 查找字符串中最后一个点，
            // 如果有则截取点到字符串末尾之间的子字符串作为扩展名
            sep_pos_ = name_.find_last_of('.');

            if (sep_pos_ != string::npos)
                ext_ = to_lower(name_.substr(sep_pos_));
        } else
            continue;

        if (t == FT_ALL || t == type_) {
            file_stat_.type = type_;
            file_stat_.name = name_;
            file_stat_.ext = ext_;
            file_stat_.path = path_;
            file_stat_.bytes = stat_.st_size;
            file_stat_.atime = stat_.st_atime;
            file_stat_.ctime = stat_.st_ctime;
            file_stat_.mtime = stat_.st_mtime;
            v.push_back(file_stat_);
            ++num_;

            if (num_ == max_num) {
                print_log(
                        LOG_WARN,
                        "Too many files or directorys in \"%s\".", path_.c_str());
                break;
            }
        }
#ifdef PLATFORM_WIN32
    } while (FindNextFile(h_find_, &wfd_));
#else
    }
#endif

#ifdef PLATFORM_WIN32
    FindClose(h_find_);
#else
    closedir(dir_);
#endif
}

} /* namespace zfilesys */

} /* namespace zpsoe */
